package cn.iocoder.yudao.module.system.config.tools;

import cn.hutool.core.io.FileUtil;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import io.vavr.Tuple;
import io.vavr.Tuple2;
import lombok.extern.slf4j.Slf4j;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * ffmpeg 视频处理
 */
@Component
@Slf4j
public class FFmpegTools {

    @Value("${ffmpeg.path}")
    private String FFMPEG_PATH;

    public Double getFPS(String videoPath) {
        try {
            FFmpegFrameGrabber grabber = FFmpegFrameGrabber.createDefault(videoPath);
            grabber.start();
            return grabber.getFrameRate();
        } catch (IOException e) {
            log.error("video duration acquisition failed :" + e.getMessage());
            return 0d;
        }
    }

    public String readVideoResolution(String inputPath) {
        try {
            FFmpegFrameGrabber grabber = FFmpegFrameGrabber.createDefault(inputPath);
            grabber.start();
            int width = grabber.getImageWidth();
            int height = grabber.getImageHeight();
            grabber.stop();
            return width + "x" + height;  // 返回分辨率
        } catch (IOException e) {
            log.error("video Resolution acquisition failed :" + e.getMessage());
            return null;
        }
    }

    public Tuple2<Long, Double> readVideo(String videoPath) {
        try {
            FFmpegFrameGrabber grabber = FFmpegFrameGrabber.createDefault(videoPath);
            grabber.start();
            Long duration = grabber.getLengthInTime() / (1000 * 1000);
            Double fps = grabber.getFrameRate();
            return Tuple.of(duration, fps);
        } catch (IOException e) {
            log.error("video duration acquisition failed :" + e.getMessage());
            return null;
        }
    }

    public static Long getDuration(String videoPath) {
        try {
            FFmpegFrameGrabber grabber = FFmpegFrameGrabber.createDefault(videoPath);
            grabber.start();
            return grabber.getLengthInTime() / (1000 * 1000);
        } catch (IOException e) {
            log.error("video duration acquisition failed :" + e.getMessage());
            return 0L;
        }
    }

    public void capFrames(String inputVideo, Long sec, String keyframeFile) {
        String command = String.format(
                "%s -i %s -ss %s -vframes 1 %s",
                FFMPEG_PATH,
                inputVideo,
                sec,
                keyframeFile
        );
        log.info(command);
        ProcessBuilder processBuilder = new ProcessBuilder(command.split(" "));
        processBuilder.redirectErrorStream(true);
        try {
            Process process = processBuilder.start();
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    log.info(line);
                }
                process.waitFor();
            }
        } catch (IOException | InterruptedException e) {
            log.error("capFrames fail:" + e.getMessage());
        }
    }

    public List<File> captureFrames(String inputVideo, int frameRate, Long duration) {
        List<String> frameList = new ArrayList<>();
        //int frameInterval = frameRate * 60;
        int frameInterval;
        if (duration >= 180) { // 180 秒（3 分钟）
            frameInterval = ((int) (duration / 4)) * frameRate; // 如果视频时长大于等于3分钟，则提取5个关键帧
        } else {
            frameInterval = ((int) (duration / 2)) * frameRate; // 否则提取3个关键帧
        }
        String outputPattern = FileUtil.getParent(inputVideo, 1);
        String outName = FileUtil.getPrefix(inputVideo) + "_%d.jpg";
        String outPath = outputPattern + "/" + FileUtil.getPrefix(inputVideo);
        String outpathPatternName = outPath + "/" + outName;
        File outFile = new File(outPath);
        if (!outFile.exists()) {
            outFile.mkdirs();
        }
        String command = String.format(
                "%s -i %s -vf select=not(mod(n\\,%d)) -vsync vfr -frame_pts true %s",
                FFMPEG_PATH,
                inputVideo,
                frameInterval,
                outpathPatternName
        );
        log.info(command);
        ProcessBuilder processBuilder = new ProcessBuilder(command.split(" "));
        processBuilder.redirectErrorStream(true);

        try {
            Process process = processBuilder.start();
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    log.info(line);
                }
                process.waitFor();
            }
            return FileUtil.loopFiles(outPath);
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 裁剪视频
     *
     * @param input     输入视频
     * @param startTime 裁剪开始的时间，格式为 HH:MM:SS
     * @param duration  裁剪的持续时间，格式为 HH:MM:SS
     * @param out       输出文件
     */
    public Boolean videoCutter(String input, String startTime, String duration, String out) {
        // -i：输入文件  -ss：开始裁剪的时间  -t：裁剪的持续时间  -c copy：避免重新编码，直接复制音频流，这样会加快处理速度且避免质量损失
        String command = String.format("%s -i %s -ss %s -t %s -c:v libx264 -c:a aac -strict experimental %s",
                FFMPEG_PATH, input, startTime, duration, out);
        log.info("audio cutter :" + command);
        ProcessBuilder processBuilder = new ProcessBuilder(command.split(" "));
        processBuilder.redirectErrorStream(true);
        try {
            Process process = processBuilder.start();
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    log.info(line);
                }
                if (process.waitFor() == 0) {
                    return true;
                } else {
                    return false;
                }
            }
        } catch (IOException | InterruptedException e) {
            log.error(e.getMessage());
            return false;
        }
    }

    /**
     * 裁剪音频
     *
     * @param input     输入视频
     * @param startTime 裁剪开始的时间，格式为 HH:MM:SS
     * @param duration  裁剪的持续时间，格式为 HH:MM:SS
     * @param out       输出文件
     */
    public Boolean audioCutter(String input, String startTime, String duration, String out) {
        // -i：输入文件  -ss：开始裁剪的时间  -t：裁剪的持续时间  -c copy：避免重新编码，直接复制音频流，这样会加快处理速度且避免质量损失
        String command = String.format("ffmpeg -i %s -ss %s -t %s -c copy %s",
                input, startTime, duration, out);
        log.info("audio cutter :" + command);
        ProcessBuilder processBuilder = new ProcessBuilder(command.split(" "));
        processBuilder.redirectErrorStream(true);
        try {
            Process process = processBuilder.start();
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    log.info(line);
                }
                if (process.waitFor() == 0) {
                    return true;
                } else {
                    return false;
                }
            }
        } catch (IOException | InterruptedException e) {
            log.error(e.getMessage());
            return false;
        }
    }

    /**
     * 转换音频编码 MP3的编码参数
     *
     *  320kbps 是指 320千比特每秒（Kilobits Per Second），用于描述音频文件的比特率（bit rate）。比特率是衡量音频文件每秒钟传输多少比特数据的一个指标，它直接影响音频的质量和文件的大小。
     *
     * 在 MP3 等音频格式中，比特率越高，音频的质量通常越好，因为每秒钟传输的数据量更多，从而可以更好地保留声音细节。但同时，文件的体积也会增加。
     *
     * 320kbps 的含义
     * 320kbps 表示每秒钟传输 320,000 位数据，也就是 320 千比特。
     * 在音频格式中，320kbps 是常见的 高质量音频 比特率之一，通常比 128kbps 和 192kbps 等低比特率提供更高的音质。
     * 相比于低比特率的音频文件，320kbps 的 MP3 文件能够保留更多的音频细节，声音更加清晰，尤其在高频和低频部分。
     * 比特率与音质的关系
     * 低比特率（如 128kbps）：音频质量较差，可能会听到明显的压缩痕迹，比如“噪声”和“失真”，尤其是在复杂的音乐或动态范围较广的音频中。
     * 中比特率（如 192kbps、256kbps）：音频质量适中，适合大多数情况下的听音需求，文件大小比 320kbps 小，但音质可能有所损失。
     * 高比特率（如 320kbps）：音质接近原始无损音频，适合需要高音质的场合，比如高品质音乐的播放和录音制作等。
     * 320kbps 音频的特点
     * 音质好：320kbps 的 MP3 文件通常可以提供相对较好的音质，足够满足大多数音乐爱好者的需求，尤其是在人耳无法察觉到明显失真的情况下。
     * 文件较大：由于比特率较高，文件大小也相应较大。比如，一首 4 分钟的歌曲，320kbps 的 MP3 文件通常大约为 9-10MB，而相同的歌曲如果是 128kbps，文件大小可能只有 3MB 左右。
     * 总结
     * 320kbps 是一种 高质量的音频压缩标准，在 MP3 等有损音频格式中，通常是较高的比特率选择，适合需要更好音质的用户。
     *
     * @param input    输入视频
     * @param bitRate  比特率  默认比特率190kbps
     * @param out      输出文件
     */
    public Boolean transcodeMP3(String input, int bitRate, String out) {
        String command = String.format(
                "%s -i %s -b:a " + bitRate + "k %s",
                FFMPEG_PATH,
                input,
                out
        );
        log.info("transcode to MP3 190kbps :" + command);
        ProcessBuilder processBuilder = new ProcessBuilder(command.split(" "));
        processBuilder.redirectErrorStream(true);
        try {
            Process process = processBuilder.start();
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    log.info(line);
                }
                if (process.waitFor() == 0) {
                    return true;
                } else {
                    return false;
                }
            }
        } catch (IOException | InterruptedException e) {
            log.error(e.getMessage());
            return false;
        }
    }
    /**
     * 转换视频为hls m3u8流文件
     *
     * @param input    输入视频
     * @param hls_time ts时长大小
     * @param out      输出文件
     */
    public Boolean convertToHLS(String input, Long hls_time, String out) {
        String command = String.format(
                "%s -i %s -codec: copy -start_number 0 -hls_time %s -hls_list_size 0 -f hls %s",
                FFMPEG_PATH,
                input,
                hls_time,
                out
        );
        log.info("convert to hls :" + command);
        ProcessBuilder processBuilder = new ProcessBuilder(command.split(" "));
        processBuilder.redirectErrorStream(true);
        try {
            Process process = processBuilder.start();
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    log.info(line);
                }
                if (process.waitFor() == 0) {
                    return true;
                } else {
                    return false;
                }
            }
        } catch (IOException | InterruptedException e) {
            log.error(e.getMessage());
            return false;
        }
    }

    public void captureFrameFromVideo(String videoPath, String outputPath, int framePositionInSeconds, int timeoutInSeconds) {
        File videoFile = new File(videoPath);
        if (!videoFile.exists()) {
            log.error(videoPath + " does not exist!");
            throw new ServiceException(501, "视频不存在");
        }
        Process ffmpegProcess = null;
        try {
            // 构造ffmpeg命令
            String command = String.format("%s -ss %d -i %s -vframes 1 -q:v 2 %s",
                    FFMPEG_PATH, framePositionInSeconds, videoPath, outputPath);
            // 执行FFmpeg命令
            ffmpegProcess = Runtime.getRuntime().exec(command);

            InputStream errorStrean = ffmpegProcess.getErrorStream();
            // 新线程处理标准错误流，防止缓冲区满导致进程阻塞
            Thread errorStreamHandler = new Thread(() -> {
                try (BufferedReader br = new BufferedReader(new InputStreamReader(errorStrean))) {
                    String line;
                    while ((line = br.readLine()) != null) {
                        log.info(line);
                    }
                } catch (IOException ioe) {
                    ioe.printStackTrace();
                }
            });
            errorStreamHandler.start();
            // 等待进程终止，指定超时时间
            boolean finished = ffmpegProcess.waitFor(timeoutInSeconds, TimeUnit.SECONDS);
            if (!finished) {
                throw new IOException("FFmpeg process timed out.");
            }
            // 检查进程退出值
            if (ffmpegProcess.exitValue() != 0) {
                throw new IOException("FFmpeg command failed to create a cover image.");
            }
            log.info("Cover image was created successfully.");
        } catch (Exception e) {
            if (ffmpegProcess != null) {
                ffmpegProcess.destroy(); // 强制终止进程
            }
            log.error(e.getMessage());// 打印堆栈轨迹
        } finally {
            if (ffmpegProcess != null) {
                closeQuietly(ffmpegProcess.getInputStream());
                closeQuietly(ffmpegProcess.getOutputStream());
                closeQuietly(ffmpegProcess.getErrorStream());
            }
        }
    }

    private static void closeQuietly(InputStream is) {
        try {
            if (is != null) {
                is.close();
            }
        } catch (IOException e) { // 忽略
        }
    }

    private static void closeQuietly(OutputStream os) {
        try {
            if (os != null) {
                os.close();
            }
        } catch (IOException e) { // 忽略
        }
    }

}
